// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/ssl/ssl_client_auth_handler.h"

#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/macros.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/resource_request_info.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/client_cert_store.h"
#include "net/url_request/url_request.h"

namespace content {

namespace {

    class ClientCertificateDelegateImpl : public ClientCertificateDelegate {
    public:
        explicit ClientCertificateDelegateImpl(
            const base::WeakPtr<SSLClientAuthHandler>& handler)
            : handler_(handler)
            , continue_called_(false)
        {
        }

        ~ClientCertificateDelegateImpl() override
        {
            if (!continue_called_) {
                BrowserThread::PostTask(
                    BrowserThread::IO, FROM_HERE,
                    base::Bind(&SSLClientAuthHandler::CancelCertificateSelection,
                        handler_));
            }
        }

        // ClientCertificateDelegate implementation:
        void ContinueWithCertificate(net::X509Certificate* cert) override
        {
            DCHECK(!continue_called_);
            continue_called_ = true;
            BrowserThread::PostTask(
                BrowserThread::IO, FROM_HERE,
                base::Bind(&SSLClientAuthHandler::ContinueWithCertificate, handler_,
                    base::RetainedRef(cert)));
        }

    private:
        base::WeakPtr<SSLClientAuthHandler> handler_;
        bool continue_called_;

        DISALLOW_COPY_AND_ASSIGN(ClientCertificateDelegateImpl);
    };

    void SelectCertificateOnUIThread(
        const ResourceRequestInfo::WebContentsGetter& wc_getter,
        net::SSLCertRequestInfo* cert_request_info,
        const base::WeakPtr<SSLClientAuthHandler>& handler)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);

        std::unique_ptr<ClientCertificateDelegate> delegate(
            new ClientCertificateDelegateImpl(handler));

        WebContents* web_contents = wc_getter.Run();
        if (!web_contents)
            return;

        GetContentClient()->browser()->SelectClientCertificate(
            web_contents, cert_request_info, std::move(delegate));
    }

} // namespace

// A reference-counted core to allow the ClientCertStore and SSLCertRequestInfo
// to outlive SSLClientAuthHandler if needbe.
class SSLClientAuthHandler::Core : public base::RefCountedThreadSafe<Core> {
public:
    Core(const base::WeakPtr<SSLClientAuthHandler>& handler,
        std::unique_ptr<net::ClientCertStore> client_cert_store,
        net::SSLCertRequestInfo* cert_request_info)
        : handler_(handler)
        , client_cert_store_(std::move(client_cert_store))
        , cert_request_info_(cert_request_info)
    {
    }

    bool has_client_cert_store() const { return !!client_cert_store_; }

    void GetClientCerts()
    {
        if (client_cert_store_) {
            // TODO(davidben): This is still a cyclical ownership where
            // GetClientCerts' requirement that |client_cert_store_| remains alive
            // until the call completes is maintained by the reference held in the
            // callback.
            client_cert_store_->GetClientCerts(
                *cert_request_info_, &cert_request_info_->client_certs,
                base::Bind(&SSLClientAuthHandler::Core::DidGetClientCerts, this));
        } else {
            DidGetClientCerts();
        }
    }

private:
    friend class base::RefCountedThreadSafe<Core>;

    ~Core() { }

    // Called when |client_cert_store_| is done retrieving the cert list.
    void DidGetClientCerts()
    {
        if (handler_)
            handler_->DidGetClientCerts();
    }

    base::WeakPtr<SSLClientAuthHandler> handler_;
    std::unique_ptr<net::ClientCertStore> client_cert_store_;
    scoped_refptr<net::SSLCertRequestInfo> cert_request_info_;
};

SSLClientAuthHandler::SSLClientAuthHandler(
    std::unique_ptr<net::ClientCertStore> client_cert_store,
    net::URLRequest* request,
    net::SSLCertRequestInfo* cert_request_info,
    SSLClientAuthHandler::Delegate* delegate)
    : request_(request)
    , cert_request_info_(cert_request_info)
    , delegate_(delegate)
    , weak_factory_(this)
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    core_ = new Core(weak_factory_.GetWeakPtr(), std::move(client_cert_store),
        cert_request_info_.get());
}

SSLClientAuthHandler::~SSLClientAuthHandler()
{
}

void SSLClientAuthHandler::SelectCertificate()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    // |core_| will call DidGetClientCerts when done.
    core_->GetClientCerts();
}

// static
void SSLClientAuthHandler::ContinueWithCertificate(
    const base::WeakPtr<SSLClientAuthHandler>& handler,
    net::X509Certificate* cert)
{
    if (handler)
        handler->delegate_->ContinueWithCertificate(cert);
}

// static
void SSLClientAuthHandler::CancelCertificateSelection(
    const base::WeakPtr<SSLClientAuthHandler>& handler)
{
    if (handler)
        handler->delegate_->CancelCertificateSelection();
}

void SSLClientAuthHandler::DidGetClientCerts()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    // Note that if |client_cert_store_| is NULL, we intentionally fall through to
    // SelectCertificateOnUIThread. This is for platforms where the client cert
    // matching is not performed by Chrome. Those platforms handle the cert
    // matching before showing the dialog.
    if (core_->has_client_cert_store() && cert_request_info_->client_certs.empty()) {
        // No need to query the user if there are no certs to choose from.
        //
        // TODO(davidben): The WebContents-less check on the UI thread should come
        // before checking ClientCertStore; ClientCertStore itself should probably
        // be handled by the embedder (https://crbug.com/394131), especially since
        // this doesn't work on Android (https://crbug.com/345641).
        BrowserThread::PostTask(
            BrowserThread::IO, FROM_HERE,
            base::Bind(&SSLClientAuthHandler::ContinueWithCertificate,
                weak_factory_.GetWeakPtr(), nullptr));
        return;
    }

    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&SelectCertificateOnUIThread,
            ResourceRequestInfo::ForRequest(request_)->GetWebContentsGetterForRequest(),
            base::RetainedRef(cert_request_info_),
            weak_factory_.GetWeakPtr()));
}

} // namespace content
